using System;
using System.Net;
using CodePlex.DependencyInjection.ObjectBuilder;
using CodePlex.TfsLibrary.ClientEngine;
using CodePlex.TfsLibrary.Utility;

namespace CodePlex.CodePlexClientLibrary
{
    public abstract class AuthenticatedCommand : NonAuthenticatedCommand
    {
        ICredentialsCache credentialsCache;
        bool doNotCacheCredentials;
        bool nonInteractive;
        string password;
        bool presentedAuth;
        ICodePlexProjectInfoService projectInfoService;
        bool triedAnonymous;
        bool triedCachedCreds;
        bool tryAnonymous = true;
        string username;

        protected ICredentialsCache CredentialsCache
        {
            get { return credentialsCache; }
        }

        [Option("no-cache-authinfo", "do not cache username/password", ShortName = "!")]
        public bool DoNotCacheCredentials
        {
            get { return doNotCacheCredentials; }
            set { doNotCacheCredentials = value; }
        }

        protected override ITfsEngine Engine
        {
            get
            {
                if (base.Engine != null && base.Engine.CredentialsCallback == null)
                    base.Engine.CredentialsCallback = OnCredentialCallback;

                return base.Engine;
            }
        }

        [Option("non-interactive", "do not prompt", ShortName = "i")]
        public bool NonInteractive
        {
            get { return nonInteractive; }
            set { nonInteractive = value; }
        }

        [Option("password", "specify your CodePlex password", ShortName = "p")]
        public string Password
        {
            get { return password; }
            set { password = value; }
        }

        protected ICodePlexProjectInfoService ProjectInfoService
        {
            get { return projectInfoService; }
        }

        protected bool TryAnonymous
        {
            get { return tryAnonymous; }
            set { tryAnonymous = value; }
        }

        [Option("username", "specify your CodePlex username", ShortName = "u")]
        public string Username
        {
            get { return username; }
            set { username = value; }
        }

        public override NetworkCredential GetCredentialsForUrl(string url)
        {
            if (string.IsNullOrEmpty(Username) && credentialsCache != null)
                return credentialsCache[Config.CodePlexWebServiceUrl ?? url];

            return new NetworkCredential(Username, Password);
        }

        [InjectionMethod]
        public void InitializeAuthenticatedCommand(ICredentialsCache credentialCache,
                                                   ICodePlexProjectInfoService codePlexProjectInfoService)
        {
            credentialsCache = credentialCache;
            projectInfoService = codePlexProjectInfoService;
        }

        protected virtual ICredentials OnCredentialCallback(ICredentials oldCredentials,
                                                            string url)
        {
            if (tryAnonymous && !triedAnonymous)
            {
                triedAnonymous = true;

                if (!string.IsNullOrEmpty(Config.AnonymousUsername))
                    return TransformForBasicAuth(new NetworkCredential(Config.AnonymousUsername, null), url);
            }

            if (!triedCachedCreds && string.IsNullOrEmpty(Username))
            {
                triedCachedCreds = true;

                NetworkCredential cachedCreds = GetCredentialsForUrl(url);

                if (cachedCreds != null || (Config.TryDefaultCredentials && !Config.ForceBasicAuth))
                    return TransformForBasicAuth(cachedCreds, url);
            }

            string user = null;
            string pass = null;

            if (presentedAuth)
                Console.WriteLine("error: Attempted to perform an unauthorized operation.");
            else
            {
                presentedAuth = true;
                user = Username;
                pass = Password;

                if ((string.IsNullOrEmpty(user) || string.IsNullOrEmpty(pass)) && !NonInteractive)
                    Console.WriteLine("Your credentials are required for {0}", url);
            }

            if (string.IsNullOrEmpty(user))
            {
                string defaultUsername;

                if (oldCredentials == null)
                    defaultUsername = Environment.UserName;
                else
                {
                    NetworkCredential netCreds = oldCredentials.GetCredential(new Uri(url), "Basic");

                    if (netCreds.Domain == "" && netCreds.UserName == Config.AnonymousUsername)
                        defaultUsername = Environment.UserName;
                    else if (netCreds.Domain != "")
                        defaultUsername = netCreds.Domain + "\\" + netCreds.UserName;
                    else
                        defaultUsername = netCreds.UserName;
                }

                if (NonInteractive)
                    return null;

                Console.Write("Username [{0}]: ", defaultUsername);
                user = ConsoleUtil.ReadLine(true);

                if (user == "")
                    user = defaultUsername;
            }

            if (string.IsNullOrEmpty(pass))
            {
                if (NonInteractive)
                    return null;

                Console.Write("Password for {0}: ", user);
                pass = ConsoleUtil.ReadLine(false);
            }

            string domain = null;
            string[] usernamePieces = user.Split(new char[] { '\\' }, 2);

            if (usernamePieces.Length > 1)
            {
                domain = usernamePieces[0];
                user = usernamePieces[1];
            }

            NetworkCredential credentials = new NetworkCredential(user, pass, domain);

            if (Config.CodePlexWebServiceUrl != null)
                TransformWithCodePlexWebSerivce(credentials);

            if (!DoNotCacheCredentials)
                SetCredentialsForUrl(url, credentials);

            return TransformForBasicAuth(credentials, url);
        }

        public void SetCredentialsForUrl(string url,
                                         NetworkCredential credentials)
        {
            if (credentialsCache != null)
                credentialsCache[Config.CodePlexWebServiceUrl ?? url] = credentials;
        }

        ICredentials TransformForBasicAuth(NetworkCredential credentials,
                                           string url)
        {
            if (credentials == null)
                return null;

            if (!Config.ForceBasicAuth)
                return credentials;

            CredentialCache cache = new CredentialCache();
            cache.Add(new Uri(url), "Basic", credentials);
            return cache;
        }

        void TransformWithCodePlexWebSerivce(NetworkCredential credentials)
        {
            if (credentials.Domain != "")
                return;

            string result = ProjectInfoService.CodePlexUsernameToTfsUsername(credentials.UserName);
            string[] pieces = result.Split(new char[] { '\\' }, 2);

            if (pieces.Length > 1)
            {
                credentials.Domain = pieces[0];
                credentials.UserName = pieces[1];
            }
            else
                credentials.UserName = result;
        }
    }
}